package com.fintech.pangu.autoconfigure.security.oauth2.client;

import com.fintech.pangu.autoconfigure.security.properties.PanGuSecurityProperties;
import com.fintech.pangu.keyvault.KeyVaultObtain;
import com.fintech.pangu.security.oauth2.sso.client.authentication.GenerateJWTAuthenticationSuccessHandler;
import org.springframework.boot.autoconfigure.security.oauth2.resource.UserInfoRestTemplateFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.http.MediaType;
import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.ExceptionHandlingConfigurer;
import org.springframework.security.oauth2.client.OAuth2RestTemplate;
import org.springframework.security.oauth2.client.filter.OAuth2ClientAuthenticationProcessingFilter;
import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter;
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher;
import org.springframework.web.accept.ContentNegotiationStrategy;
import org.springframework.web.accept.HeaderContentNegotiationStrategy;

import java.util.Collections;

/**
 * OAuth2 Client SSO安全配置
 */
public class OAuth2SsoSecurityConfigurer {

    private ApplicationContext applicationContext;

    /**
     * PanGu安全相关的所有配置
     */
    private PanGuSecurityProperties panGuSecurityProperties;

    /**
     * OAuth2 Client关于授权码流程相关配置
     */
    //private AuthorizationCodeResourceDetails authorizationCodeResourceDetails;

    /**
     * 获取用户信息资源服务相关配置
     */
    //private ResourceServerProperties resourceServerProperties;

    /**
     * OAuth2 Client上下文
     */
    //private OAuth2ClientContext oauth2ClientContext;


    /**
     * OAuth2 Client SSO安全配置 构造方法
     * @param applicationContext
     * @param panGuSecurityProperties  PanGu安全相关的所有配置
     */
    public OAuth2SsoSecurityConfigurer(ApplicationContext applicationContext, PanGuSecurityProperties panGuSecurityProperties
            /*, AuthorizationCodeResourceDetails authorizationCodeResourceDetails, ResourceServerProperties resourceServerProperties*/) {
        this.applicationContext = applicationContext;
        this.panGuSecurityProperties = panGuSecurityProperties;
        //this.authorizationCodeResourceDetails = authorizationCodeResourceDetails;
        //this.resourceServerProperties = resourceServerProperties;
    }


    /**
     * OAuth2 SSO子配置入口
     * @param http
     * @throws Exception
     */
    public void configure(HttpSecurity http) throws Exception {
        // 应用OAuth2 Client SSO认证相关配置
        http.apply(new OAuth2ClientAuthenticationConfigurer(oauth2SsoFilter()));

        // 添加跳转到 pangu.oauth2.sso.client.loginPath 的 AuthenticationEntryPoint
        addAuthenticationEntryPoint(http);
    }


    /**
     * OAuth2 SSO Filter
     * @return
     */
    private OAuth2ClientAuthenticationProcessingFilter oauth2SsoFilter() {
        OAuth2ClientAuthenticationProcessingFilter oauth2SsoFilter = new OAuth2ClientAuthenticationProcessingFilter(panGuSecurityProperties.getOauth2().getSsoClient().getLoginPath());

        /**
         * 使用SsoSecurityConfigurer中获取OAuth2RestTemplate、ResourceServerTokenServices逻辑
         * 均通过ResourceServerTokenServicesConfiguration创建
         */
        OAuth2RestTemplate restTemplate = this.applicationContext.getBean(UserInfoRestTemplateFactory.class).getUserInfoRestTemplate();
        oauth2SsoFilter.setRestTemplate(restTemplate);

        ResourceServerTokenServices tokenServices = this.applicationContext.getBean(ResourceServerTokenServices.class);
        oauth2SsoFilter.setTokenServices(tokenServices);

        /**
         * （暂不使用）自定义创建OAuth2RestTemplate、ResourceServerTokenServices
         */
        //OAuth2RestTemplate restTemplate = new OAuth2RestTemplate(authorizationCodeResourceDetails, getOauth2ClientContext());
        //oauth2SsoFilter.setRestTemplate(restTemplate);

        // userInfo
        //UserInfoTokenServices tokenService = new UserInfoTokenServices(resourceServerProperties.getUserInfoUri(), authorizationCodeResourceDetails.getClientId());
        //tokenServices.setRestTemplate(restTemplate);
        //oauth2SsoFilter.setTokenServices(tokenService);

        // check_token
        //RemoteTokenServices tokenService = new RemoteTokenServices();
        //tokenService.setCheckTokenEndpointUrl(resourceServerProperties.getTokenInfoUri());
        //tokenService.setClientId(resourceServerProperties.getClientId());
        //tokenService.setClientSecret(resourceServerProperties.getClientSecret());
        //oauth2SsoFilter.setTokenServices(tokenService);


        oauth2SsoFilter.setApplicationEventPublisher(applicationContext);
        oauth2SsoFilter.setAuthenticationSuccessHandler(generateJWTAuthenticationSuccessHandler());  // 自定义OAuth2 Client SSO成功处理器

        return oauth2SsoFilter;
    }


    /**
     * OAuth2 SSO登录成功处理器
     * 构造用户信息JWT存入session，之后跳转到 /oauthUserDetails
     */
    public GenerateJWTAuthenticationSuccessHandler generateJWTAuthenticationSuccessHandler() {
        GenerateJWTAuthenticationSuccessHandler generateJWTAuthenticationSuccessHandler = new GenerateJWTAuthenticationSuccessHandler();
        //generateJWTAuthenticationSuccessHandler.setDefaultTargetUrl("/oauthUserDetails"); //登录成功后不跳转到固定的URL，根据缓存请求的参数做重定向
        generateJWTAuthenticationSuccessHandler.setKeyVaultObtain(applicationContext.getBean(KeyVaultObtain.class));
        return generateJWTAuthenticationSuccessHandler;
    }

    /**
     * 使用SecurityConfigurer为filter设置SessionAuthenticationStrategy，此时已经经过SessionManagementConfigurer设置SessionAuthenticationStrategy
     * 1、给OAuth2ClientAuthenticationProcessingFilter配置SessionAuthenticationStrategy
     * 2、将OAuth2ClientAuthenticationProcessingFilter添加到过滤器链
     */
    private class OAuth2ClientAuthenticationConfigurer
            extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {

        private OAuth2ClientAuthenticationProcessingFilter filter;

        OAuth2ClientAuthenticationConfigurer(OAuth2ClientAuthenticationProcessingFilter filter) {
            this.filter = filter;
        }

        @Override
        public void configure(HttpSecurity httpSecurity) throws Exception {
            OAuth2ClientAuthenticationProcessingFilter ssoFilter = this.filter;
            ssoFilter.setSessionAuthenticationStrategy(httpSecurity.getSharedObject(SessionAuthenticationStrategy.class));
            httpSecurity.addFilterAfter(ssoFilter, AbstractPreAuthenticatedProcessingFilter.class);
        }
    }



    /**
     * OAuth2 SSO登录的AuthenticationEntryPoint
     *
     * @param httpSecurity
     * @throws Exception
     */
    private void addAuthenticationEntryPoint(HttpSecurity httpSecurity) throws Exception {
        ExceptionHandlingConfigurer<HttpSecurity> exceptions = httpSecurity.exceptionHandling();
        ContentNegotiationStrategy contentNegotiationStrategy = httpSecurity.getSharedObject(ContentNegotiationStrategy.class);
        if (contentNegotiationStrategy == null) {
            contentNegotiationStrategy = new HeaderContentNegotiationStrategy();
        }
        MediaTypeRequestMatcher preferredMatcher = new MediaTypeRequestMatcher(
                contentNegotiationStrategy, MediaType.APPLICATION_XHTML_XML,
                new MediaType("image", "*"), MediaType.TEXT_HTML, MediaType.TEXT_PLAIN);
        preferredMatcher.setIgnoredMediaTypes(Collections.singleton(MediaType.ALL));

        //exceptions.defaultAuthenticationEntryPointFor(
        //        new UnauthorizedEntryPoint(),
        //        new RequestHeaderRequestMatcher("X-Requested-With", "XMLHttpRequest"));

        // 添加跳转到登录页面EntryPoint
        exceptions.defaultAuthenticationEntryPointFor(
                new LoginUrlAuthenticationEntryPoint(panGuSecurityProperties.getOauth2().getSsoClient().getLoginPath()),
                preferredMatcher);
        // When multiple entry points are provided the default is the first one
    }

}
